<template>
  <component class="w-image-wrap" :is="wrapperTag" :class="wrapperClasses" :style="wrapperStyles">
    <transition :name="transition" appear="appear">
      <component class="w-image" v-if="loaded" :is="tag" :class="imageClasses" :style="imageStyles"
        :src="tag === 'img' ? imgSrc : null"></component>
    </transition>
    <div class="w-image__loader" v-if="!noSpinner && loading">
      <slot v-if="$slots.loading" name="loading"></slot>
      <w-progress v-else circle="circle" indeterminate="indeterminate"></w-progress>
    </div>
    <component class="w-image__content" v-if="$slots.default" :is="wrapperTag" :class="contentClass">
      <slot></slot>
    </component>
  </component>
</template>

<script>
  /**
   * Different use cases of w-image:
   * - fixed size: no width and no height (use bg)
   * - fixed size: given width + height (use bg)
   * - adaptive size: given ratio + width 100% (use bg)
   * - adaptive size: given ratio + height 100% (use bg)
   * - adaptive & locked size: given width or height and using <img>
   **/

  // @todo handle figure, captions, srcset, webp.
  import { consoleWarn } from "../../utils/console";
  import { ref, computed, onMounted, watch, getCurrentInstance } from "vue";
  export default {
    name: "w-image",
    props: {
      tag: { type: String, default: "span" },
      src: { type: String },
      width: { type: [Number, String] },
      height: { type: [Number, String] },
      ratio: { type: [Number, String] },
      lazy: { type: Boolean },
      absolute: { type: Boolean },
      fixed: { type: Boolean },
      contain: { type: Boolean },
      noSpinner: { type: Boolean },
      fallback: { type: String },
      transition: { type: String, default: "fade" },
      contentClass: { type: [String, Array, Object] }
    },

    emits: ["loading", "loaded", "error"],
    setup(props, ctx) {
      const loading = ref(false);
      const loaded = ref(false);
      const imgSrc = ref("");
      const imgWidth = ref(props.width || 0);
      const imgHeight = ref(props.height || 0);
      const imgComputedRatio = ref(0);
      const instance = getCurrentInstance();

      // computed
      const imgGivenRatio = computed(() => {
        return parseFloat(props.ratio);
      });

      const wrapperTag = computed(() => {
        return ["span", "div"].includes(props.tag) ? props.tag : "span";
      });

      const wrapperClasses = computed(() => {
        return {
          "w-image-wrap--absolute": props.absolute,
          "w-image-wrap--fixed": props.fixed,
          "w-image-wrap--has-ratio": imgGivenRatio.value
        };
      });

      const wrapperStyles = computed(() => {
        return {
          width: imgGivenRatio.value
            ? null
            : (!isNaN(imgWidth.value) ? `${imgWidth.value}px` : imgWidth.value) ||
            null,
          height:
            imgGivenRatio.value || props.tag === "img"
              ? null
              : (!isNaN(imgHeight.value)
                ? `${imgHeight.value}px`
                : imgHeight.value) || null,
          "padding-bottom": imgGivenRatio.value && `${imgGivenRatio.value * 100}%`
        };
      });

      const imageClasses = computed(() => {
        return {
          "w-image--loading": loading.value,
          "w-image--loaded": loaded.value,
          "w-image--contain": props.contain
        };
      });

      const imageStyles = computed(() => {
        return {
          "background-image":
            props.tag !== "img" && loaded.value ? `url('${imgSrc.value}')` : null
        };
      });

      function loadImage(loadFallback = false) {
        // Don't try to reload image if already loading.
        if (loading.value) return;

        loading.value = true;
        loaded.value = false;
        ctx.emit("loading", loadFallback ? props.fallback : props.src);

        return new Promise(resolve => {
          const img = new Image();
          img.onload = e => {
            if (!props.width && !props.height && !imgGivenRatio.value) {
              imgWidth.value = e.target.width;
              imgHeight.value = e.target.height;
            }
            imgComputedRatio.value = e.target.height / e.target.width;

            loading.value = false;
            loaded.value = true;
            imgSrc.value = loadFallback ? props.fallback : props.src;
            ctx.emit("loaded", imgSrc.value);

            return resolve(img);
          };
          img.onerror = error => {
            ctx.emit("error", error);
            // If a fallback is provided & not already trying to load it, load the fallback src.
            if (props.fallback && !loadFallback) {
              loading.value = false;
              loadImage(true);
            } else {
              loading.value = false;
              loaded.value = false;
            }

            // return reject(error)
          };

          img.src = loadFallback ? props.fallback : props.src;
        });
      }

      // hooks
      onMounted(() => {
        if (!props.src) {
          return consoleWarn("The w-image component was used without src.");
        }

        if (props.lazy) {
          const IntersectObserver = new IntersectionObserver(entry => {
            if (entry[0] && entry[0].isIntersecting) {
              loadImage();
              IntersectObserver.disconnect();
            }
          }, instance.ctx.intersectionConfig);
          IntersectObserver.observe(instance.vnode.el);
        } else {
          loadImage();
        }
      });
      // watch
      watch(
        () => props.src,
        () => {
          loadImage();
        }
      );
      watch(
        () => props.width,
        value => {
          imgWidth.value = value;
        }
      );

      watch(
        () => props.height,
        value => {
          imgHeight.value = value;
        }
      );
      return {
        loading,
        loaded,
        imgSrc,
        imgWidth,
        imgHeight,
        imgComputedRatio,
        // computed
        imgGivenRatio,
        wrapperTag,
        wrapperClasses,
        wrapperStyles,
        imageClasses,
        imageStyles,
        // methods
        loadImage
      };
    },

  };
</script>